﻿/**
* @authors <darl2ng@gmail.com, ngocanh.ngo@gmail.com>
* @version 0.9
* Copyright 2010
* 
* The MIT License
* Copyright (c) 2010 <darl2ng@gmail.com, ngocanh.ngo@gmail.com>
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.ng.ResHelp {

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.ProgressEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.text.Font;
	import flash.utils.getDefinitionByName;
	import XML;
	
	public class c_ResLoader extends EventDispatcher{
		
		public static var EXTERNAL_LIBRARIES_LOADED:String = "EXTERNAL_LIBRARIES_LOADED";
		public static var EXTERNAL_LIBRARIES_LOADING_STARTED:String = "EXTERNAL_LIBRARIES_LOADING_STARTED";
		public static var PREPARATION_LOADED:String = "PREPARATION_LOADED";
		public static var PREPARATION_LOADING_STARTED:String = "PREPARATION_STARTED";
		
		static private var __Instance:c_ResLoader;
		
		protected var __XMLPath:String;
		protected var __XML:XML;
		
		protected var __ExternalBytesTotal:Number = 0; //excluding the config XML
		protected var __PreparationBytesLoaded:Number = 0;
		protected var __PreparationBytesTotal:Number = 0;
		
		protected var __IsLoaded:Boolean = false;
		protected var __IsStarted:Boolean = false;
		
		private var __LoadingsManager:c_LoadingsManager;
		protected var __LoadersManager:c_LoadersManager;
		
		protected var __Classes:Object;
		
		static public function GetInstance():c_ResLoader {
			if(__Instance == null)
				__Instance = new c_ResLoader();
			return __Instance;
		}		
		public function c_ResLoader() {
			__Classes = new Object();
		}
		
		/**
		 * Init directly by xmlData
		 * @param	xmlData
		 */
		public function initXML(xmlData:String):void {
			__XML = new XML(xmlData);
			__LoadingsManager = new c_LoadingsManager();			
			__LoadersManager = new c_LoadersManager();			
			initLoadingChannel('auto');
		}	
		/**
		 * Init by loading external xml file
		 * @param	xmlFile
		 */
		public function init(xmlFile:String):void {
			var xmlLoader:URLLoader = new URLLoader(); 
			xmlLoader.addEventListener(Event.COMPLETE, OnConfigXMLLoaded);
			xmlLoader.addEventListener(ProgressEvent.PROGRESS, OnConfigXMLProgress);
			xmlLoader.load(new URLRequest(xmlFile));
		}	
		
		/**
		 * for editor which can open any file
		 */
		public function set XMLPath(xmlPath:String):void {
			__XMLPath = xmlPath;
		}		
		
		public function get classes():Object {
			return __Classes;
		}
		public function get isLoaded():Boolean {
			return __IsLoaded;
		}
		public function get isStarted():Boolean {
			return  __IsStarted;
		}
		public function get PreparationBytesTotal():uint {
			return __PreparationBytesTotal;
		}		
		public function get PreparationBytesLoaded():uint {
			return __PreparationBytesLoaded;
		}
		public function get xml(): XML {
			return __XML;
		}
		public function get loadersManager(): c_LoadersManager{
			return __LoadersManager;
		}
		public function get externalBytesLoaded():uint {
			var l_loaded:uint = 0;
			if (__ExternalBytesTotal != 0) {				
				for each (var loader:c_Loader in __LoadersManager.loaders) {			
					l_loaded += loader.bytesLoaded;
				}
			}
			return l_loaded;
		}
		public function get externalBytesTotal():uint {			
			return __ExternalBytesTotal;
		}
		
		/**
		 * @param	id
		 */
		public function getClassById(id:String):Class {			
			var cl:Class = __Classes[id];
			if (cl != null) {
				//e = new cl();// just try to instantiate => removed so that it does not interfere with auto-register mechanism if any
			} else {
				try {							
					cl = getDefinitionByName(id) as Class;
				} catch (e:ReferenceError) {
					throw new Error("c_ResLoader::ClassNotExist[" + id +"]");
				}
			}			
			return cl;
		}
		
		public function initLoadingDelayChannel():void {
			initLoadingChannel('delay');
		}
		public function initLoadingOnDemandChannel():void {
			initLoadingChannel('onDemand');
		}

		/**
		 * @param	p_Loading
		 */
		protected function registerClasses(p_Loading:String):void {
			for each (var swf:XML in __XML.Loadings.Swfs.Swf.(@loading == p_Loading)) {
				var swfid:String = swf.attribute("id");				
				for each (var item:XML in __XML.Loadings.Swfs.Swf.(@id == swfid).Class) {								
					var id:String = item.attribute("id");
					if (id == null || id == '') {
						throw new Error("c_ResLoader::registerClasses[null]");
					}
					var cl:Class = __LoadersManager.getLoaderById(swfid).getClassById(id);
					if (cl != null) {
						__Classes[id] = cl;						
						if (item.attribute("isFont")=="true") {							
							try {
								//note: Fonts loaded from other domains can't be regsitered
								Font.registerFont(cl);
							} catch ( e:Error ) {
								// traces "ArgumentError: Error #1508: The value specified for 
								// argument font is invalid." when the font.swf comes from another domain
								throw new Error("c_ResLoader::ArgumentError[" + e.message +"]");
							}
						}
					}
				}
			}			
		}		
		
		private function OnConfigXMLProgress(e:ProgressEvent):void {
			__PreparationBytesLoaded = e.bytesLoaded;
			if (__PreparationBytesTotal==0) {
				__PreparationBytesTotal = e.bytesTotal;
				dispatchEvent(new Event(PREPARATION_LOADING_STARTED));
			}
		}
		private function OnConfigXMLLoaded(e:Event):void {
			(e.target as URLLoader).removeEventListener(Event.COMPLETE, OnConfigXMLLoaded);
			(e.target as URLLoader).removeEventListener(ProgressEvent.PROGRESS, OnConfigXMLProgress);
			__XML = new XML(e.target.data);
			__LoadingsManager = new c_LoadingsManager();			
			__LoadersManager = new c_LoadersManager();
			dispatchEvent(new Event(PREPARATION_LOADED));
			initLoadingChannel('auto');
		}
		/**
		 * @param	p_ChannelID
		 */
		private function OnSwfsLoaded(p_ChannelID:String):void {
			__IsLoaded = true;
			registerClasses(p_ChannelID);
			dispatchEvent(new Event(EXTERNAL_LIBRARIES_LOADED));
		}
		private function OnSwfsLoadingStarted(p_ChannelID:String):void {
			if (!__IsStarted) {
				__IsStarted = true;
				for each (var loader:c_Loader in __LoadersManager.loaders) {
					__ExternalBytesTotal += loader.bytesTotal;											
				}					
				dispatchEvent(new Event(EXTERNAL_LIBRARIES_LOADING_STARTED));
			}
		}
		private function initLoadingChannel(p_Loading:String):void {
			
			for each (var lib:XML in __XML.Loadings.Swfs.Swf.(@loading == p_Loading)) {
				
				var swf_id:String = lib.attribute("id");
				var swf_path:String = __XML.Loadings.Swfs.Swf.(@id == swf_id).attribute("path");	
				
				var path: String;
				if (__XMLPath) {
					path = __XMLPath + '/' + swf_path; //editor mode  needs this
				} else {
					path = swf_path;
				}
				//We can group all the files into one loading channel
				__LoadingsManager.getLoadingChannelById(p_Loading).setCompleteListener(OnSwfsLoaded);
				__LoadingsManager.getLoadingChannelById(p_Loading).setStartedListener(OnSwfsLoadingStarted);
				__LoadersManager.load(swf_id, path, __LoadingsManager.getLoadingChannelById(p_Loading));
			}			
		}		
	}
}